| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- import { getServerSideConfig } from "@/app/config/server";
- import {
- TENCENT_BASE_URL,
- ApiPath,
- ModelProvider,
- ServiceProvider,
- Tencent,
- } from "@/app/constant";
- import { prettyObject } from "@/app/utils/format";
- import { NextRequest, NextResponse } from "next/server";
- import { auth } from "@/app/api/auth";
- import { isModelAvailableInServer } from "@/app/utils/model";
- import CryptoJS from "crypto-js";
- import mapKeys from "lodash-es/mapKeys";
- import mapValues from "lodash-es/mapValues";
- import isArray from "lodash-es/isArray";
- import isObject from "lodash-es/isObject";
- const serverConfig = getServerSideConfig();
- async function handle(
- req: NextRequest,
- { params }: { params: { path: string[] } },
- ) {
- console.log("[Tencent Route] params ", params);
- if (req.method === "OPTIONS") {
- return NextResponse.json({ body: "OK" }, { status: 200 });
- }
- const authResult = auth(req, ModelProvider.Hunyuan);
- if (authResult.error) {
- return NextResponse.json(authResult, {
- status: 401,
- });
- }
- try {
- const response = await request(req);
- return response;
- } catch (e) {
- console.error("[Tencent] ", e);
- return NextResponse.json(prettyObject(e));
- }
- }
- export const GET = handle;
- export const POST = handle;
- export const runtime = "edge";
- export const preferredRegion = [
- "arn1",
- "bom1",
- "cdg1",
- "cle1",
- "cpt1",
- "dub1",
- "fra1",
- "gru1",
- "hnd1",
- "iad1",
- "icn1",
- "kix1",
- "lhr1",
- "pdx1",
- "sfo1",
- "sin1",
- "syd1",
- ];
- async function request(req: NextRequest) {
- const controller = new AbortController();
- // tencent just use base url or just remove the path
- let path = `${req.nextUrl.pathname}`.replaceAll(
- ApiPath.Tencent + "/" + Tencent.ChatPath,
- "",
- );
- let baseUrl = serverConfig.tencentUrl || TENCENT_BASE_URL;
- if (!baseUrl.startsWith("http")) {
- baseUrl = `https://${baseUrl}`;
- }
- if (baseUrl.endsWith("/")) {
- baseUrl = baseUrl.slice(0, -1);
- }
- console.log("[Proxy] ", path);
- console.log("[Base Url]", baseUrl);
- const timeoutId = setTimeout(
- () => {
- controller.abort();
- },
- 10 * 60 * 1000,
- );
- const fetchUrl = `${baseUrl}${path}`;
- let body = null;
- if (req.body) {
- const bodyText = await req.text();
- console.log(
- "Dogtiti ~ request ~ capitalizeKeys(JSON.parse(bodyText):",
- capitalizeKeys(JSON.parse(bodyText)),
- );
- body = JSON.stringify(capitalizeKeys(JSON.parse(bodyText)));
- }
- const fetchOptions: RequestInit = {
- headers: {
- ...getHeader(body),
- },
- method: req.method,
- body: '{"Model":"hunyuan-pro","Messages":[{"Role":"user","Content":"你好"}]}', // FIXME
- redirect: "manual",
- // @ts-ignore
- duplex: "half",
- signal: controller.signal,
- };
- // #1815 try to refuse some request to some models
- if (serverConfig.customModels && req.body) {
- try {
- const clonedBody = await req.text();
- fetchOptions.body = clonedBody;
- const jsonBody = JSON.parse(clonedBody) as { model?: string };
- // not undefined and is false
- if (
- isModelAvailableInServer(
- serverConfig.customModels,
- jsonBody?.model as string,
- ServiceProvider.Tencent as string,
- )
- ) {
- return NextResponse.json(
- {
- error: true,
- message: `you are not allowed to use ${jsonBody?.model} model`,
- },
- {
- status: 403,
- },
- );
- }
- } catch (e) {
- console.error(`[Tencent] filter`, e);
- }
- }
- console.log("[Tencent request]", fetchOptions.headers, req.method);
- try {
- const res = await fetch(fetchUrl, fetchOptions);
- console.log("[Tencent response]", res.status, " ", res.headers, res.url);
- // to prevent browser prompt for credentials
- const newHeaders = new Headers(res.headers);
- newHeaders.delete("www-authenticate");
- // to disable nginx buffering
- newHeaders.set("X-Accel-Buffering", "no");
- return new Response(res.body, {
- status: res.status,
- statusText: res.statusText,
- headers: newHeaders,
- });
- } finally {
- clearTimeout(timeoutId);
- }
- }
- function capitalizeKeys(obj: any): any {
- if (isArray(obj)) {
- return obj.map(capitalizeKeys);
- } else if (isObject(obj)) {
- return mapValues(
- mapKeys(
- obj,
- (value: any, key: string) => key.charAt(0).toUpperCase() + key.slice(1),
- ),
- capitalizeKeys,
- );
- } else {
- return obj;
- }
- }
- // 使用 SHA-256 和 secret 进行 HMAC 加密
- function sha256(message: any, secret = "", encoding = "hex") {
- const hmac = CryptoJS.HmacSHA256(message, secret);
- if (encoding === "hex") {
- return hmac.toString(CryptoJS.enc.Hex);
- } else if (encoding === "base64") {
- return hmac.toString(CryptoJS.enc.Base64);
- } else {
- return hmac.toString();
- }
- }
- // 使用 SHA-256 进行哈希
- function getHash(message: any, encoding = "hex") {
- const hash = CryptoJS.SHA256(message);
- if (encoding === "hex") {
- return hash.toString(CryptoJS.enc.Hex);
- } else if (encoding === "base64") {
- return hash.toString(CryptoJS.enc.Base64);
- } else {
- return hash.toString();
- }
- }
- function getDate(timestamp: number) {
- const date = new Date(timestamp * 1000);
- const year = date.getUTCFullYear();
- const month = ("0" + (date.getUTCMonth() + 1)).slice(-2);
- const day = ("0" + date.getUTCDate()).slice(-2);
- return `${year}-${month}-${day}`;
- }
- function getHeader(payload: any) {
- // https://cloud.tencent.com/document/api/1729/105701
- // 密钥参数
- const SECRET_ID = serverConfig.tencentSecretId;
- const SECRET_KEY = serverConfig.tencentSecretKey;
- const endpoint = "hunyuan.tencentcloudapi.com";
- const service = "hunyuan";
- const region = ""; // optional
- const action = "ChatCompletions";
- const version = "2023-09-01";
- const timestamp = Math.floor(Date.now() / 1000);
- //时间处理, 获取世界时间日期
- const date = getDate(timestamp);
- // ************* 步骤 1:拼接规范请求串 *************
- const hashedRequestPayload = getHash(payload);
- const httpRequestMethod = "POST";
- const canonicalUri = "/";
- const canonicalQueryString = "";
- const canonicalHeaders =
- "content-type:application/json; charset=utf-8\n" +
- "host:" +
- endpoint +
- "\n" +
- "x-tc-action:" +
- action.toLowerCase() +
- "\n";
- const signedHeaders = "content-type;host;x-tc-action";
- const canonicalRequest =
- httpRequestMethod +
- "\n" +
- canonicalUri +
- "\n" +
- canonicalQueryString +
- "\n" +
- canonicalHeaders +
- "\n" +
- signedHeaders +
- "\n" +
- hashedRequestPayload;
- // ************* 步骤 2:拼接待签名字符串 *************
- const algorithm = "TC3-HMAC-SHA256";
- const hashedCanonicalRequest = getHash(canonicalRequest);
- const credentialScope = date + "/" + service + "/" + "tc3_request";
- const stringToSign =
- algorithm +
- "\n" +
- timestamp +
- "\n" +
- credentialScope +
- "\n" +
- hashedCanonicalRequest;
- // ************* 步骤 3:计算签名 *************
- const kDate = sha256(date, "TC3" + SECRET_KEY);
- const kService = sha256(service, kDate);
- const kSigning = sha256("tc3_request", kService);
- const signature = sha256(stringToSign, kSigning, "hex");
- // ************* 步骤 4:拼接 Authorization *************
- const authorization =
- algorithm +
- " " +
- "Credential=" +
- SECRET_ID +
- "/" +
- credentialScope +
- ", " +
- "SignedHeaders=" +
- signedHeaders +
- ", " +
- "Signature=" +
- signature;
- return {
- Authorization: authorization,
- "Content-Type": "application/json; charset=utf-8",
- Host: endpoint,
- "X-TC-Action": action,
- "X-TC-Timestamp": timestamp.toString(),
- "X-TC-Version": version,
- "X-TC-Region": region,
- };
- }
|